/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.data.json.model;

import cn.hermit.data.json.validator.ValidatorChain;
import cn.hermit.data.exception.FactoryException;
import cn.hermit.data.exception.ValidationException;
import cn.hermit.data.json.JSON;

import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * JSON Factory to create JSON objects
 * 
 * <p>
 * In <a href="http://json.org" target="_blank">JSON specification</a>, 
 * there is no items about JSON order and JSON duplicated Keys. 
 * But some cases requires to keep order of JSON keys or keep duplicated keys.
 * The framework provides many factories to create various kinds of JSON Object, which allow duplicated keys or keep keys order.
 * Please see the package of {@link com.eqxiu.tiny.data.json.model various factories }
 * </p>
 * 
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 20 Jun, 2014
 */
public abstract class JSONFactory {

	private ValidatorChain validator;

	/**
	 * Constructor of JSONFactory
	 * 
	 * <p>
	 * Initialize the validator chain inside the constructor
	 * </p>
	 */
	public JSONFactory() {
		validator = new ValidatorChain();
	}

	/**
	 * Create a container of JSON Object
	 * 
	 * @return
	 * 		a container of JSON Object
	 */
	public abstract Map<String, JSONDocument> createObjectContainer();

	/**
	 * Create a container of JSON Array
	 * 
	 * @return
	 * 		a container of JSON Array
	 */
	public abstract Collection<JSONDocument> createArrayContainer();

	/**
	 * Create JSON Object
	 * 
	 * @return
	 * 		JSON Object
	 */
	public JSONObject createObject() {
		JSONObject jo = new JSONObject();
		jo.entries = this.createObjectContainer();
		return jo;
	}

	/**
	 * Create JSON Array
	 * 
	 * @return
	 * 		JSON Array
	 */
	public JSONArray createArray() {
		JSONArray ja = new JSONArray();
		ja.values = this.createArrayContainer();
		return ja;
	}

	/**
	 * Create JSON Value with Java plain Object
	 * 
	 * @param value
	 * 		Java plain Object
	 * @return
	 * 		JSON Value
	 */
	public JSONValue createValue(Object value) {
		return createValue(value, true);
	}

	/**
	 * Create JSON Value with Java plain Object
	 * 
	 * @param value
	 * 		Java plain Object
	 * @param isText
	 * 		If the JSON Value is string type
	 * @return
	 * 		JSON Value
	 */
	public JSONValue createValue(Object value, boolean isText) {
		try {
			value = validator.validateValue(value);
			if (value == null){
				value = JSON.DEFAULT_NULL_VALUE;
				isText = false;
			}
		} catch (ValidationException e) {
			throw new FactoryException(e);
		}
		JSONValue jv = new JSONValue();
		jv.value = value;
		jv.isText = isText;
		return jv;
	}

	/**
	 * Append JSON Entry with key, value typed JSONDocument to JSON Object
	 * 
	 * @param object
	 * 		JSON Object
	 * @param key
	 * 		Entry key
	 * @param value
	 * 		Entry value typed JSONDocument
	 */
	public void objectEntry(JSONObject object, String key, JSONDocument value) {
		try {
			key = validator.validateKey(key);
			if (key == null)
				key = JSON.DEFAULT_NULL_VALUE;
		} catch (ValidationException e) {
			throw new FactoryException(e);
		}
		value.parent = object;
		object.entries.put(key, value);
	}

	/**
	 * Append JSON Entry with key, value typed Java plain Object to JSON Object
	 * 
	 * @param object
	 * 		JSON Object
	 * @param key
	 * 		Entry key
	 * @param value
	 * 		Entry value typed Java plain Object
	 */
	public void objectEntry(JSONObject object, String key, Object value) {
		JSONValue jv = this.createValue(value);
		this.objectEntry(object, key, jv);
	}

	/**
	 * Append JSON Entry with key, value typed Java plain Object to JSON Object
	 * 
	 * @param object
	 * 		JSON Object
	 * @param key
	 * 		Entry key
	 * @param value
	 * 		Entry value typed Java plain Object
	 * @param isText
	 * 		If the JSON Value is string type
	 */
	public void objectEntry(JSONObject object, String key, Object value,
			boolean isText) {
		JSONValue jv = this.createValue(value, isText);
		this.objectEntry(object, key, jv);
	}

	/**
	 * Append JSON Entries to JSON Object
	 * 
	 * @param object
	 * 		JSON Object
	 * @param entries
	 * 		JSON Entries
	 * 
	 */
	public void objectEntries(JSONObject object,
			Map<String, JSONDocument> entries) {
		if (entries == null || entries.size() == 0) {
			return;
		}
		object.entries.putAll(entries);
	}

	/**
	 * Append values typed JSONDocument to JSON Array
	 * @param array
	 * 		JSON Array
	 * @param value
	 * 		Values typed JSONDocument
	 */
	public void arrayValue(JSONArray array, JSONDocument... value) {
		if (value == null || value.length == 0) {
			return;
		}
		for (JSONDocument v : value) {
			v.parent = array;
			array.values.add(v);
		}
	}

	/**
	 * Append values typed Java plain Object to JSON Array
	 * @param array
	 * 		JSON Array
	 * @param value
	 * 		Values typed Java plain Object
	 */
	public void arrayValue(JSONArray array, Object... value) {
		if (value == null || value.length == 0) {
			return;
		}
		for (Object v : value) {
			JSONValue jv = this.createValue(v);
			this.arrayValue(array, jv);
		}
	}

	/**
	 * Append values typed Java plain Object to JSON Array
	 * @param array
	 * 		JSON Array
	 * @param value
	 * 		Values typed Java plain Object
	 * @param isText
	 * 		If the JSON Value is string type
	 * 
	 */
	public void arrayValue(JSONArray array, Object value, boolean isText) {
		JSONValue jv = this.createValue(value, isText);
		this.arrayValue(array, jv);
	}

	/**
	 * Append values typed JSONDocument to JSON Array
	 * @param array
	 * 		JSON Array
	 * @param values
	 * 		values typed JSONDocument
	 */
	public void arrayValues(JSONArray array, List<JSONDocument> values) {
		if (values == null || values.size() == 0) {
			return;
		}
		array.values.addAll(values);
	}

	/**
	 * Get Validator chain initialized in constructor.
	 * 
	 * @return
	 * 		Validator chain
	 */
	public ValidatorChain getValidator() {
		return validator;
	}
	

}